Перейти к основному содержимому

MDX-безопасное написание

Версия: 1.1 Дата: 26.04.2026 Статус: Готов к обсуждению

Документ описывает технику безопасности при создании и правке любого MD-документа, индексируемого DocMap и рендерящегося через Docusaurus. Несоблюдение этих правил ломает сборку (npm run build) и блокирует деплой.

Изменения в v1.1 (26.04.2026): добавлен Класс 2 ловушек MDX — фигурные скобки парсятся как JSX-выражение и при отсутствии переменной вызывают ReferenceError во время server-side rendering. Описан реальный инцидент 26.04.2026 в Диагностика CMS — порядок проверок.md: конструкция backtick внутри backtick через экранирование (см. секцию «Класс 2» ниже) уронила сборку с ошибкой path is not defined. Добавлена обязательная третья проверка через grep по фигурным скобкам. Единственный надёжный способ показывать фигурные скобки — fenced code block с языковым тегом.

Корневая причина

Docusaurus использует MDX-парсер — расширение Markdown, в котором допустимы JSX-теги. MDX трактует любой < за которым сразу идёт цифра или буква как начало JSX-тега или компонента.

<2s → парсер ищет JSX-компонент <2s ... />
<500ms → то же
<TagName> → парсер ищет компонент TagName

Если такая последовательность встречается в обычном тексте (не внутри backtick-кода) — компиляция MDX падает с ошибкой Unexpected character или Unexpected token.

Это прямой источник поломки сборки даже если документ выглядит как обычный markdown.

Класс 2 — фигурные скобки как JSX-выражение

MDX-парсер интерпретирует любые фигурные скобки в тексте как JavaScript-выражение (JSX expression). Если внутри скобок имя без объявления — ReferenceError: <name> is not defined при server-side rendering, сборка Docusaurus падает с Can't render static file.

Этот класс опаснее Класса 1 — обычный grep <digit его не ловит, а ошибка проявляется только при npm run build, не на этапе записи документа.

Опасные паттерны Класса 2

В обычном тексте (вне fenced code block):

  • Локализационные ключи с шаблонными подстановками (например, Path '%{path}' already exists с открывающей фигурной скобкой) — MDX парсит подстановку как JSX-выражение, переменной в области видимости нет, ошибка.
  • JavaScript-шаблонные строки ${variable} — то же самое.
  • Объекты в YAML или JSON в plain-тексте ({ widget: string, label: Path }) — открывающая скобка запускает JSX-парсинг.
  • Двойные фигурные {{ slug }} — парсятся как JSX-фрагмент.

В одиночном backtick-инлайне защита ненадёжна — в зависимости от версии Docusaurus и MDX-парсера фигурные скобки внутри backtick могут парситься как JSX. Не полагаться.

Антипаттерн (реальный инцидент 26.04.2026)

В документе cms-system/reference/Диагностика CMS — порядок проверок.md была попытка показать цитату из исходников Decap CMS через вложенный inline-code: внешний backtick-инлайн, а внутри — экранированный backtick через обратный слэш. Сама проблемная конструкция (показана здесь в fenced code block для безопасности):

`pathExists: \`Path '%{path}' already exists\``

MDX не понимает такое экранирование. Парсер закрывает внешний backtick после первого экранированного backtick, потом начинает читать оставшееся как обычный текст. В этом «обычном тексте» оказывается шаблонная подстановка с открывающей фигурной скобкой, MDX видит её и парсит как JSX-выражение, ищет переменную path в области видимости компонента — её нет.

Результат при npm run build:

Error: Can't render static file for pathname "/cms-system/reference/Диагностика CMS — порядок проверок"
[cause]: ReferenceError: path is not defined

Исправление — вынос в отдельный fenced code block с языковым тегом:

pathExists: "Path '%{path}' already exists"

Внутри fenced code MDX не парсит JSX вообще — ни тегов, ни выражений. Безопасно для любого содержимого, включая фигурные скобки, шаблонные подстановки, JS-объекты.

Безопасные формы для Класса 2

Единственный надёжный способ — fenced code block с указанием языка:

```js
pathExists: "Path '%{path}' already exists"
```

```yaml
meta: { path: { widget: string, label: Path } }
```

```bash
curl -X POST "$URL" -d '{"action":"info"}'
```

Внутри fenced code block MDX не парсит JSX вообще — ни тегов, ни выражений. Это работает на всех версиях Docusaurus и MDX 2/3.

HTML-entity для редких случаев в plain-тексте:

  • &#123; — открывающая фигурная скобка;
  • &#125; — закрывающая фигурная скобка.

Это рендерится визуально как фигурные скобки, но парсер MDX entity не считает за начало JSX-выражения.

Что НЕ работает для Класса 2

  • ❌ Экранирование через обратный слэш — \{ \} не работает в MDX 2/3.
  • ❌ Двойные фигурные {{ ... }} — это JSX-фрагмент, тоже парсится.
  • ❌ Одиночный backtick-инлайн с фигурными — ненадёжен, не гарантирует.
  • ❌ Backtick внутри backtick через ``` — этот приём ломает inline code и провоцирует JSX-парсинг.

Правило

При любом упоминании фигурных скобок в документации DocMap — выносить в fenced code block с языковым тегом.

Это касается:

  • цитат из исходников языков программирования (JS, TS, Python — везде есть фигурные скобки);
  • примеров YAML/JSON-конфигов (содержат фигурные скобки для объектов);
  • упоминаний шаблонных строк (${variable}, {{ slug }});
  • примеров локализационных ключей с подстановками;
  • любых JS-выражений в обычном тексте.

Опасные конструкции

Запрещено в plain-тексте документа:

  • <2s — парсер ищет JSX-компонент <2s>.
  • <500ms — то же.
  • <1.5s, <15 min, <10% — то же.
  • <TagName> — если не намеренный JSX-компонент Docusaurus, парсер пытается срендерить как компонент.
  • >2 сервиса — обычно работает, но небезопасно при некоторых конфигурациях парсера, лучше избегать.

Безопасные формы

Вариант 1. HTML-entity (предпочтительный)

&lt;2s вместо <2s
&lt;500ms вместо <500ms
&gt;2 сервиса вместо >2 сервиса

Визуально в браузере выглядит идентично исходному <2s или >2 сервиса. Парсер MDX не путает с JSX-тегом.

Вариант 2. Пробел после <

< 2s
< 500ms

Подходит, если стилистически уместно. Парсер MDX не считает < за стартом JSX-тега, если после идёт пробел.

Вариант 3. Backtick-инлайн

`<2s`
`<TagName>`

Внутри backtick-кода MDX не парсит JSX. Безопасно для технических примеров.

Вариант 4. Fenced code block

```
<2s — это безопасно внутри блока кода
<TagName>
```

Внутри ``` ``` MDX не парсит JSX. Безопасно для блоков примеров.

Обязательная проверка после записи документа

После каждого docs_create_file или docs_patch_section — самопроверка через grep, три команды:

# Класс 1: < или > перед цифрой
grep -nE '<[0-9]' путь/к/файлу.md
grep -nE '>[0-9]' путь/к/файлу.md

# Класс 2: открывающая фигурная скобка
grep -nE '\{' путь/к/файлу.md

Каждое совпадение проверяется визуально:

  • Внутри fenced code block ``` (тройной backtick) — безопасно, оставляем.
  • Внутри одиночного backtick-инлайна — для Класса 1 безопасно, для Класса 2 ненадёжно. Для фигурных скобок переоформить в fenced.
  • В plain-тексте — обязательно переоформить.

Автоисправление для Класса 1 (массовая замена):

sed -i 's/<\([0-9]\)/\&lt;\1/g' путь/к/файлу.md

Эта команда заменяет каждый < за которым идёт цифра — на HTML-entity и эту цифру. Обратный слэш перед & обязателен в shell, чтобы избежать раскрытия истории.

Автоисправление для Класса 2 невозможно — каждый случай требует ручной переработки в fenced code block с правильным языковым тегом (js, yaml, bash, json и так далее).

Случай из практики

24-25 апреля 2026 — после массовой записи 50+ архитектурных документов в vitiana-api-platform/ сборка Docusaurus упала с десятками ошибок MDX. Причина — везде <2s, <500ms, <1.5s в обычном тексте.

Решение — массовая замена через sed по всем затронутым файлам:

find docs/vitiana-api-platform -name '*.md' -exec sed -i 's/<\([0-9]\)/\&lt;\1/g' {} \;

После замены — npm run build прошёл без ошибок MDX. Документы визуально не изменились (&lt;2s и <2s рендерятся одинаково).

Урок: проверять MDX-safe до записи, а не после падения сборки. Чек-лист — в Правила оформления документов.

Тонкости

Что НЕ ломается

  • < без цифры/буквы сразу после: a < b (с пробелами) — безопасно.
  • < в URL: https://example.com/path?a=<value> — обычно безопасно, но лучше экранировать или поместить в backtick.
  • HTML-entity &lt; &gt; — всегда безопасно.
  • Внутри `code` или ``` block ``` — всегда безопасно.

Что ломается реже, но возможно

  • > за которым цифра: >2 сервиса — на некоторых версиях MDX компилируется, на других нет. Лучше &gt;2 сервиса для надёжности.
  • Текст похожий на JSX-атрибут: <div className="foo"> в plain-тексте — парсится как открытие компонента div, что иногда проходит, иногда нет. Лучше backtick.

Точное правило MDX

Парсер MDX считает < началом JSX-тега, если сразу после него идёт:

  • буква (a-z, A-Z) — открытие именованного компонента;
  • > — фрагмент <>;
  • / — закрытие тега </…>.

Цифра формально не должна включать JSX-парсинг по спецификации MDX 2/3, но на практике в Docusaurus с интеграцией ремарка/рехайпа конструкция <2s вызывает синтаксическую ошибку. Поэтому правило — экранировать <digit в любом случае.

Связанная документация